/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.spring.web.handler;

import com.google.common.collect.Maps;
import org.springframework.core.Ordered;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * 用于处理 @ResponseBody 标注方法异常的解析器
 *
 * @author vacoor
 */
public class ResponseBodyHandlerMethodExceptionResolver extends AbstractHandlerMethodExceptionResolver {
    private List<HttpMessageConverter<?>> messageConverters;

    public ResponseBodyHandlerMethodExceptionResolver() {
        setOrder(Ordered.LOWEST_PRECEDENCE - 1);
    }

    @Override
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {
        logger.error(request.getRequestURI(), exception);

        if (null == handlerMethod || null == handlerMethod.getMethodAnnotation(ResponseBody.class)) {
            return null;
        }

        try {
            return handleResponseBody(request, response, exception);
        } catch (IOException e) {
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    private ModelAndView handleResponseBody(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException {
        Map<String, Object> value = Maps.newHashMap();
        value.put("success", false);
        value.put("message", ex.getMessage());

        HttpInputMessage inputMessage = new ServletServerHttpRequest(request);
        List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
        if (acceptedMediaTypes.isEmpty()) {
            acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
        }
        MediaType.sortByQualityValue(acceptedMediaTypes);
        HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);
        Class<?> retType = value.getClass();

        List<HttpMessageConverter<?>> messageConverters = getMessageConverters();
        if (null != messageConverters) {
            for (MediaType acceptedMediaType : acceptedMediaTypes) {
                for (HttpMessageConverter messageConverter : messageConverters) {
                    if (messageConverter.canWrite(retType, acceptedMediaType)) {
                        messageConverter.write(value, acceptedMediaType, outputMessage);
                        return new ModelAndView();
                    }
                }
            }
        }
        if (logger.isWarnEnabled()) {
            logger.warn("Could not find HttpMessageConverter that supports return type [" + retType + "] and " + acceptedMediaTypes);
        }
        return null;
    }

    public List<HttpMessageConverter<?>> getMessageConverters() {
        return messageConverters;
    }

    public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        this.messageConverters = messageConverters;
    }
}
